using System;
using System.Collections.Generic;
using System.Linq;

namespace foundation.structure.link
{
    public delegate bool LinkAction<in T>(T obj);

    public class Link<T>
    {
        public LinkVo<T> First;
        public LinkVo<T> Last;
        public bool Lock = false;
        public readonly List<LinkVo<T>> Waitlist = new List<LinkVo<T>>();


        private LinkVo<T> GetLinkVoByWeight(int weight)
        {
            for (var vo = First; null != vo; vo = vo.Next)
            {
                if (vo.Weight > weight)
                {
                    return vo;
                }
            }

            return Last;
        }


        private void DoAddVo(LinkVo<T> vo)
        {
            if (null == First)
            {
                First = Last = vo;
                return;
            }

            if (vo.Weight > First.Weight)
            {
                First.Pre = vo;
                vo.Next = First;
                First = vo;
                return;
            }

            if (Last.Weight >= vo.Weight)
            {
                Last.Next = vo;
                vo.Pre = Last;
                Last = vo;
                return;
            }


            var parent = GetLinkVoByWeight(vo.Weight);
            var parentNext = parent.Next;

            parent.Next = vo;
            vo.Pre = parent;
            parentNext.Pre = vo;
            vo.Next = parentNext;
        }


        private static LinkVo<T> CreateVo(T value, int weight)
        {
            var vo = ClassUtils.PoolGet<LinkVo<T>>();
            vo.Close = false;
            vo.Weight = weight;
            vo.Data = value;
            return vo;
        }

        private static void DisposeVo(LinkVo<T> vo)
        {
            vo.Dispose();
            ClassUtils.PoolSet(vo);
        }


        public LinkVo<T> Find(T value)
        {
            for (var vo = First; null != vo; vo = vo.Next)
            {
                if (value.Equals(vo.Data))
                {
                    return vo;
                }
            }

            return Waitlist.FirstOrDefault(vo => value.Equals(vo.Data));
        }

        public LinkVo<T> Find(T value, object thisobj)
        {
            for (var vo = First; null != vo; vo = vo.Next)
            {
                if (value.Equals(vo.Data) && thisobj.Equals(vo.thisobj))
                {
                    return vo;
                }
            }

            return Waitlist.FirstOrDefault(vo => value.Equals(vo.Data) && thisobj.Equals(vo.thisobj));
        }


        public LinkVo<T> Push(T value, int weight = 0)
        {
            var vo = CreateVo(value, weight);
            if (Lock)
            {
                Waitlist.Add(vo);
            }
            else
            {
                DoAddVo(vo);
            }
            return vo;
        }

        public T Pop()
        {
            if (Lock)
            {
                throw new Exception("Link Locked");
            }

            if (Last == null) return default(T);
            var data = Last.Data;
            Last.Close = true;

            return data;
        }


        public void Remove(T value)
        {
            var vo = Find(value);
            if (null != vo)
            {
                vo.Clean();
            }
        }

        public void Clean()
        {
            while (First is { Close: true })
            {
                var next = First.Next;
                DisposeVo(First);
                First = next;
            }

            while (Last is { Close: true })
            {
                var pre = Last.Pre;
                DisposeVo(Last);
                Last = pre;
            }
        }


        public void ForEach(LinkAction<LinkVo<T>> handler)
        {
            Lock = true;
            for (var vo = First; null != vo; vo = vo.Next)
            {
                if (vo.Close) continue;
                if (false == handler(vo)) break;
            }

            Lock = false;
            Waitlist.ForEach(DoAddVo);
            Waitlist.Clear();
        }


        public void ForEachData(LinkAction<T> handler)
        {
            Lock = true;
            for (var vo = First; null != vo; vo = vo.Next)
            {
                if (vo.Close) continue;
                if (false == handler(vo.Data)) break;
            }

            Lock = false;
            Waitlist.ForEach(DoAddVo);
            Waitlist.Clear();
        }

        public List<LinkVo<T>> Vos
        {
            get
            {
                var link = new List<LinkVo<T>>();
                for (var vo = First; null != vo; vo = vo.Next)
                {
                    if (vo.Close == false)
                    {
                        link.Add(vo);
                    }
                }

                return link;
            }
        }

        public List<T> Datas
        {
            get
            {
                var link = new List<T>();
                for (var vo = First; null != vo; vo = vo.Next)
                {
                    if (vo.Close == false)
                    {
                        link.Add(vo.Data);
                    }
                }

                return link;
            }
        }
    }
}